Skip to main content

Backtest Reports

The framework generates self-contained HTML dashboard reports for analyzing backtest results. Reports work for both single and multi-strategy backtests — no external dependencies required.

Quick Start

from investing_algorithm_framework import BacktestReport

# Single strategy report
report = BacktestReport(backtest)
report.show() # Opens in browser (or renders inline in Jupyter)

Creating Reports

From a Backtest Object

After running a backtest, pass the result directly:

backtest = app.run_backtest(
backtest_date_range=backtest_range,
initial_amount=1000
)

report = BacktestReport(backtest)
report.show(browser=True)

From Multiple Backtests

Compare strategies side by side in a single dashboard:

backtest_a = app.run_backtest(...)
backtest_b = app.run_backtest(...)

report = BacktestReport(backtests=[backtest_a, backtest_b])
report.show()

This generates a multi-strategy comparison dashboard with:

  • Strategy ranking tables (Key Metrics, Trading Activity)
  • Return Scenarios projections (Good/Average/Bad/Very Bad Year)
  • Normalized equity curves overlay
  • Per-strategy detail pages with Summary, Runs, and Performance tabs
  • Compare mode with monthly return distribution (Rows/Heatmap × Returns/Growth toggles)

From Saved Backtests on Disk

Load previously saved backtests from a directory:

report = BacktestReport.open(directory_path="./my_backtests")
report.show()

The open() method recursively finds all valid backtest directories (containing algorithm_id.json and a runs/ folder) and any .iafbt bundle files, and loads them into a single report.

Optimized .iafbt bundle format

Backtests are saved by default in the framework's custom .iafbt bundle format — a single binary file per backtest combining zstd compression and MessagePack encoding. It is purpose-built for backtest reports: ~21× smaller and ~27× fewer files than the legacy directory format, and BacktestReport.open() loads it ~3× faster. The legacy directory format is still fully supported for backwards compatibility, and you can mix both in the same folder.

For very large batches, opt into parallel loading:

report = BacktestReport.open(directory_path="./my_backtests", workers=4)

You can also combine disk and in-memory backtests:

report = BacktestReport.open(
backtests=[my_new_backtest],
directory_path="./saved_backtests"
)
report.show()

Recalculating Metrics

When metric calculations are updated in a newer framework version, previously saved backtests may have stale metrics. Use recalculate_backtests_in_directory to recompute all per-run and summary metrics from the raw portfolio snapshots and trades — directly on disk, without ever loading the full set of backtests into memory:

from investing_algorithm_framework import recalculate_backtests_in_directory

# Rewrites every bundle in ./my_backtests in place
recalculate_backtests_in_directory("./my_backtests")

Each backtest is loaded, recalculated, and written back inside a worker process, so the parent process's memory footprint stays flat regardless of how many backtests are processed. This is the recommended approach for any non-trivial batch (hundreds to thousands of backtests with portfolio snapshots and trades can otherwise consume tens of GB).

Write to a different directory instead of in place:

recalculate_backtests_in_directory(
src_dir="./my_backtests",
dst_dir="./my_backtests_v2",
)

Use a custom risk-free rate (otherwise each backtest's stored rate is used):

recalculate_backtests_in_directory("./my_backtests", risk_free_rate=0.04)

Limit which metrics are recomputed, or tune parallelism:

recalculate_backtests_in_directory(
"./my_backtests",
metrics=["cagr", "sharpe_ratio", "max_drawdown", "win_rate"],
workers=4,
show_progress=True,
)

For each backtest, the function:

  1. Recomputes per-run BacktestMetrics from raw portfolio_snapshots and trades
  2. Regenerates BacktestSummaryMetrics by aggregating the updated per-run metrics
  3. Writes the updated bundle back to disk and (by default) refreshes index.parquet
Deprecated: recalculate_backtests(List[Backtest])

The in-memory variant recalculate_backtests(backtests) is deprecated since 8.7.2 and will be removed in a future major release. Holding many backtests in the parent process is memory-unsafe — each Backtest carries portfolio snapshots, trades and timeseries, so a list of a few thousand backtests can easily consume tens of GB before any work starts. Use recalculate_backtests_in_directory(src_dir, ...) instead.

Saving Reports

Save the report as a standalone HTML file you can share or open later:

report = BacktestReport(backtests=[backtest_a, backtest_b])
report.save("strategy_comparison.html")

The output is a single .html file with all CSS, JavaScript, and data embedded — no server or internet connection needed to view it.

Viewing in Jupyter

show() automatically detects Jupyter notebooks and renders the dashboard inline:

# In a Jupyter notebook cell:
report = BacktestReport.open(directory_path="./backtests")
report.show() # Renders inline in the notebook
report.show(browser=True) # Also opens in the browser

Dashboard Features

Overview Page

  • KPI cards: Best CAGR, best Sharpe, lowest max drawdown (with dual values when a window is selected)
  • Window Coverage: Strategy × window matrix showing data coverage
  • Key Metrics table: Sortable ranking with CAGR, Sharpe, Sortino, Calmar, Max DD, Volatility, Recovery Factor, Net Gain %
  • Trading Activity table: Profit Factor, Win Rate, Trades/yr, Trades/mo, Trades/wk, # Trades, Avg Return, Avg Duration
  • Return Scenarios: Good/Average/Bad/Very Bad Year projections based on CAGR ± volatility
  • Equity curves: Normalized percentage growth overlay
  • Collapsible cards: All chart sections can be collapsed/expanded

Strategy Pages

Each strategy gets a dedicated page with three tabs:

TabContents
SummaryFull KPI grid (CAGR, Sharpe, Sortino, Calmar, Max DD, Profit Factor, Win Rate, Volatility, Recovery Factor, etc.)
RunsBacktest run comparison table, equity overlay across runs
PerformanceMonthly returns heatmap, yearly returns bar chart, return distribution

Use the run selector pills to switch between summary view and individual backtest runs.

Compare Mode (Multi-Strategy)

Open the strategy selection modal to pick strategies for comparison. You can set a challenger strategy for highlighting. The compare page includes:

  • Key Metrics and Trading Activity ranking tables
  • Return Scenarios projections
  • Monthly Returns with four view modes (Returns/Growth × Rows/Heatmap), plus a year filter
  • Side-by-side equity curves and drawdown overlays
  • Metric bar charts (CAGR, Sharpe, Sortino, Calmar, Max DD, Win Rate, Profit Factor)
  • Return distribution histograms and correlation matrix
  • Rolling Sharpe ratio chart
  • Yearly returns bar charts

The page title bar with the window selector stays visible as you scroll.

Dark / Light Theme

Toggle between dark and light mode using the sun icon in the top-right corner.

Example: Full Workflow

from datetime import datetime, timezone
from investing_algorithm_framework import (
create_app, BacktestDateRange, BacktestReport,
recalculate_backtests_in_directory,
)

app = create_app()
# ... configure strategies, market, portfolio ...

# Run backtests across multiple time periods
date_ranges = [
BacktestDateRange(
start_date=datetime(2022, 1, 1, tzinfo=timezone.utc),
end_date=datetime(2022, 12, 31, tzinfo=timezone.utc),
name="2022"
),
BacktestDateRange(
start_date=datetime(2023, 1, 1, tzinfo=timezone.utc),
end_date=datetime(2023, 12, 31, tzinfo=timezone.utc),
name="2023"
),
]

app.run_vector_backtests(
strategies=my_strategies,
backtest_date_ranges=date_ranges,
initial_amount=1000,
backtest_storage_directory="./backtests"
)

# Optional: recalculate metrics with updated calculations (memory-safe, on disk)
recalculate_backtests_in_directory("./backtests", risk_free_rate=0.04)

# Generate and save the comparison report
report = BacktestReport.open(directory_path="./backtests")
report.save("comparison_report.html")
report.show(browser=True)

API Reference

BacktestReport

MethodDescription
BacktestReport(backtests=[...])Create a report from one or more Backtest objects
BacktestReport(backtest)Create a report from a single Backtest (backward compatible)
BacktestReport.open(directory_path=..., backtests=[...])Load backtests from disk and/or combine with in-memory backtests
report.show(browser=False)Display the report. In Jupyter: renders inline. Otherwise: opens browser. Set browser=True to force browser.
report.save(path)Save the report as a self-contained HTML file

recalculate_backtests_in_directory

Stream-recalculates every backtest bundle on disk inside worker processes. The full Backtest never crosses the process boundary, so parent memory stays flat.

ParameterTypeDescription
src_dirstr | PathDirectory containing .iafbt bundles (and/or legacy backtest directories)
dst_dirstr | Path, optionalOutput directory. If None, bundles are rewritten in place inside src_dir
risk_free_ratefloat, optionalOverride risk-free rate. If None, uses each backtest's stored rate
metricsList[str], optionalSpecific metrics to compute. If None, computes all default metrics
workersint, optionalNumber of parallel worker processes. Defaults to min(8, cpu_count). Pass 1 for serial
show_progressboolDisplay a tqdm progress bar (default False)
include_ohlcvboolRe-emit attached OHLCV data with the bundle (default False)
max_tasks_per_childint, optionalRecycle each worker after this many tasks so RSS stays bounded (default 16)
update_indexboolRewrite index.parquet in the destination directory (default True)

Returns: int — the number of backtests recalculated.

recalculate_backtests (deprecated)

Deprecated since 8.7.2

Use recalculate_backtests_in_directory instead. This in-memory variant will be removed in a future major release.

ParameterTypeDescription
backtestsList[Backtest]The backtests to recalculate (mutated in place and returned)
risk_free_ratefloat, optionalOverride risk-free rate. If None, uses each backtest's stored rate (falls back to 0.0)
metricsList[str], optionalSpecific metrics to compute. If None, computes all default metrics

Returns: List[Backtest] — the same backtest objects with updated metrics.